package com.gonsin.gmessage.logic.websocket;

import com.gonsin.gmessage.annotations.GMessageEnvironment;
import com.gonsin.gmessage.logic.model.GMessageModel;
import com.gonsin.gmessage.logic.model.GMessagePageRequest;
import com.gonsin.gmessage.logic.model.GMessagePageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


// TODO 此处高并发会有问题，需要优化

/**
 * WebSocket的session容器
 */
@Slf4j
@Component
public class WebSocketSessionContainer implements ApplicationContextAware {

//    private Map<String, Set<WebSocketSession>> cmdSession = new HashMap<>();       // 指令和Session的关系

    private Map<String, Map<String, Set<WebSocketSession>>> zoomSession = new HashMap<>();     // Username  -  Session

    private Map<String, WebSocketSession> totalSession = new ConcurrentHashMap<>();            // 所有Session

//    private boolean monitorOnline = false;

    private ReadWriteLock lock = new ReentrantReadWriteLock();

    private static WebSocketSessionContainer instance;
    public static WebSocketSessionContainer getInstance(){
        return instance;
    }



    /**
     * 有新的Session连接
     * @param session
     */
    public void addSession(String sessionId, WebSocketSession session) {
        lock.writeLock().lock();
        try {
            session.getSessionInfo().setConnectTime(new Date());
            totalSession.put(sessionId, session);
            if(GMessageEnvironment.LOG){
                log.info("username = 【{}】上线", session.getUsername());
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * session离线
     */
    public void removeSession(@NotNull WebSocketSession session) {
        lock.writeLock().lock();
        try {
            Set<SessionInfo.ZoomCmd> cmds = session.getRegisteredCmd();

            for(SessionInfo.ZoomCmd cmd : cmds){
                Map<String, Set<WebSocketSession>> cmdSession = zoomSession.get(cmd.getZoom());
                if(cmdSession == null){
                    continue;
                }
                Set<WebSocketSession> sessions = cmdSession.get(cmd.getCmd());
                if(sessions == null){
                    continue;
                }
                sessions.remove(session);
            }

            totalSession.remove(session.getSessionInfo().getSessionId());
            if(GMessageEnvironment.LOG) {
                log.info("username = 【{}】下线", session.getUsername());
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 将Session添加到指令集合中，该Session就可以收到该指令
     */
    public void addCmdSession(String zoom, String cmd, WebSocketSession session){
        lock.writeLock().lock();
        try {
            if(zoom == null){
                zoom = "all";
            }
            Map<String, Set<WebSocketSession>> cmdSession = zoomSession.computeIfAbsent(zoom, k -> new HashMap<>());
            Set<WebSocketSession> sessions = cmdSession.computeIfAbsent(cmd, k -> new HashSet<>());
            sessions.add(session);
            session.addToCmd(zoom, cmd);
            if (GMessageEnvironment.LOG) {
                log.info("zoom = 【{}】注册消息【{}】", zoom, cmd);
            }
        } finally {
            lock.writeLock().unlock();
        }
    }


    /**
     * 移除指令
     */
    public void removeCmdSession(String zoom, String cmd, WebSocketSession session){
        lock.writeLock().lock();
        try {
            if(zoom == null){
                zoom = "all";
            }
            Map<String, Set<WebSocketSession>> cmdSession = zoomSession.get(zoom);
            if(cmdSession == null){
                return;
            }

            Set<WebSocketSession> sessions = cmdSession.get(cmd);
            if(sessions == null){
                return;
            }
            sessions.remove(session);

            if (GMessageEnvironment.LOG) {
                log.info("zoom = 【{}】注销消息【{}】", zoom, cmd);
            }
        } finally {
            session.removeCmd(zoom, cmd);
            lock.writeLock().unlock();
        }
    }



    /**
     * 对已注册指令的session发送指令
     */
    public void sendCmd(@Nullable String zoom, GMessageModel message){
        lock.readLock().lock();
        try {
            if(zoom == null){
                zoom = "all";
            }
            Map<String, Set<WebSocketSession>> cmdMap = zoomSession.computeIfAbsent(zoom, k -> new HashMap<>());
            Set<WebSocketSession> sessions = cmdMap.get(message.getCmd().getFirstcmd());
            if(CollectionUtils.isEmpty(sessions)){
                return;
            }

            sessions.forEach(s -> {
                s.write(message);
            });

        } finally {
            lock.readLock().unlock();
        }
    }


    /**
     * 获取在线连接信息
     */
    public GMessagePageResult<SessionInfo> getOnline(GMessagePageRequest pagePo){
        List<WebSocketSession> sessions = new ArrayList<>(totalSession.values());
        if(pagePo == null){
            pagePo = new GMessagePageRequest();
            pagePo.setPage(0);
            pagePo.setCount(100000);
        }
        int first = pagePo.getPage() * pagePo.getCount();
        List<SessionInfo> infos = new ArrayList<>();
        for(int i = 0; i < pagePo.getCount(); i++){
            int index = i + first;
            if(index > sessions.size() - 1){
                break;
            }
            infos.add(sessions.get(i + first).getSessionInfo());
        }
        int size = sessions.size();
        return GMessagePageResult.newInstance(infos, pagePo.getPage(), size / pagePo.getCount() + size % pagePo.getCount() == 0 ? 0 : 1, size);
    }

    public void addUsernameSession(String username, WebSocketSession session) {
        lock.writeLock().lock();
        try {
//            Set<WebSocketSession> sessions = zoomSession.computeIfAbsent(username, k -> new HashSet<>());
//            sessions.add(session);
            session.setUsername(username);
            if(GMessageEnvironment.LOG){
                log.info("username = 【{}】已注册", session.getUsername());
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 获取一个session
     */
    public WebSocketSession getSession(String id) {
        return totalSession.get(id);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        instance = this;
    }
}
